﻿# encoding:utf-8
# 封装一些用户保存相关的业务功能
import time, random, threading
from db.wxpyDbAccessFrq import *
from db.wxpyDbUser import *
from wxpyUpload import *

g_updateFriendLock = threading.Lock()

# 启线程更新用户并保存
def updateFriendsThread(bot, dayGap):
    # 同一时刻最多一个线程
    if not g_updateFriendLock.acquire(False):
        logInfo('updateFriendsThrea ing')
        return

    logInfo('updateFriendsThrea lock start: '+str(g_updateFriendLock))
    thread = threading.Thread(target=updateFriends,args=(bot,dayGap))
    thread.setDaemon(True)
    thread.start()

# 更新数据库中保存的好友信息
# bot: 当前登录用户
# dayGap: 隔多少天更新一次，单位天
def updateFriends(bot, dayGap):
    rand = random.randint(5,10)
    logInfo('updateFriend sleep : '+str(rand))
    time.sleep(rand)

    UPDATE_FRIEND_TYPE = 'Bot.friends'
    try:
        dbd = dbData()
        # 获取上次更新好友日期
        latestAccess = dbGetAccessFrqLatest(dbd, bot.self.puid, UPDATE_FRIEND_TYPE)
        if latestAccess is not None:
            logInfo('updateFriend latest: '+str(latestAccess['create_time']))
            cut = latestAccess['create_time'].now()-latestAccess['create_time']
            if cut.days<dayGap :
                logInfo('updateFriend less than dayGap: '+str(cut))
                return

        # 否则是要更新的

        # 取所有好友数据
        timeStart = time.perf_counter()
        friends = bot.friends(True)
        timeEnd = time.perf_counter()
        timeSpend = int((time.perf_counter()-timeStart)*1000000000) # 秒乘10的9次方
        dbAddAccessFrq(dbd, bot.self, UPDATE_FRIEND_TYPE, timeSpend)

        timeStart = time.perf_counter()
        for f in friends:
            addOrUpdateUser(dbd, f)
        
        timeEnd = time.perf_counter()
        logInfo('batch add user spend time: '+str(timeEnd-timeStart))
    except Exception as e:
        dealException()
        logError('updateFriend err: '+str(e))
    else:
        logInfo('updateFriend success')
    finally:
        g_updateFriendLock.release()
        logInfo('updateFriendsThrea lock release: '+str(g_updateFriendLock))
        closeDb(dbd)

# 由于 addOrUpdateUser 调用频率很高，因此为每个 puid 加一个锁
g_addOrUpdateUserRunning = {}
# 锁 g_addOrUpdateUserRunning 用
g_addOrUpdateUserRunningLock = threading.Lock()

# 记录更新当前登录用户最近一次的时间戳(time.perf_counter())
g_curUserUpdateLatest = 0

# 新增或更新用户信息
def addOrUpdateUser(dbd, user):
    if user.puid is None:
        logError('addOrUpdateUse puid empty: '+str(user))
        # 这种情况没有属性，不知道是哪来的
        if '<Chat: None>'==str(user):
            return
        try: # 防止 user.raw 不存在
            logError('addOrUpdateUse puid empty2: '+str(user.raw))
        except Exception as e:
            dealException()
            logError('addOrUpdateUse err: '+str(e))
        
        return
    
    # 如果是自己的信息，一天更新一次
    global g_curUserUpdateLatest
    if isCurUser(user):
        curCounter = time.perf_counter()
        if curCounter-g_curUserUpdateLatest<1000*60*60*24:
            return
        logInfo('start update current user, old: '+g_curUserUpdateLatest+' now: '+curCounter)
        g_curUserUpdateLatest = curCounter
    
    lock = None
    with g_addOrUpdateUserRunningLock:
        # 是否已有锁
        logInfo('addOrUpdateUse lock in: '+ str(user.puid))
        if user.puid in g_addOrUpdateUserRunning.keys():
            logInfo('addOrUpdateUse has lock: '+ str(user.puid))
            lock = g_addOrUpdateUserRunning[user.puid]
            if not lock.acquire(False):
                # 拿不到锁证明已经有线程在跑，直接退出
                logInfo('addOrUpdateUse locking: '+ str(user.puid))
                return
            # else: 拿到锁往下走
            logInfo('addOrUpdateUse get lock: '+ str(user.puid))
        else:
            lock = threading.Lock()
            lock.acquire()
            g_addOrUpdateUserRunning[user.puid] = lock
            logInfo('addOrUpdateUse no lock: '+ str(user.puid)+' lockNum: '+str(len(g_addOrUpdateUserRunning)))
            
        logInfo('addOrUpdateUse lock out: '+str(user.puid))
        logInfo('addOrUpdateUse lock start: '+str(lock))

    dbAddUser(dbd, user)

    lock.release()
    logInfo('addOrUpdateUse lock release: '+str(lock))

# 把 user_big 及 user 中数据更新到 user_all 中
def updateFromUserBig2UserAll(dbd):
    puids = dbGetAllUserDictsByFileName(dbd, 'puid')
    if puids is None or len(puids)==0:
        logError('updateFromUserBig2UserAl not user data')
        return

    logInfo('updateFromUserBig2UserAl start:'+id(puids)+'user num: '+len(puids))
    timeStart = time.perf_counter()
    for puid in puids:
        updateFromUserBig2UserPuid(dbd, puid)

    timeEnd = time.perf_counter()
    logInfo('updateFromUserBig2UserAl end '+id(puids))
    logInfo('updateFromUserBig2UserAl spend time: '+str(timeEnd-timeStart))

# 把指定 puid 从 user_big 及 user 中数据更新到 user_all 中
# 返回是否成功
def updateFromUserBig2UserPuid(dbd, puid):
    dbUserBig = dbGetUserBig(dbd,puid)
    if dbUserBig is None:
        logError('updateFromUserBig2UserPui puid empty: '+puid)
        return False

    try:
        # 转成 dict 对象
        tUser = json.loads(dbUserBig['raw'])
        userBig = camelcase2UnderscoreDictKeys(tUser, getUserRawExceptKeys())

        dbUserDict = {}
        isgroup = userBig['is_group']
        oldUserAll = dbGetUserAll(dbd, puid, isgroup)
        if oldUserAll is None:
            dbUserDict = userBig
        else:
            # 若 user_all 中的新，只把 userAll 为默认值的项替换掉
            if oldUserAll['update_time']>dbUserBig['update_time']:
                for k in oldUserAll.keys():
                    if hasattr(userBig, k) and isDbUserAllDefaultValue(oldUserAll[k]):
                        oldUserAll[k] = userBig[k]
                        logInfo('updateFromUserBig2UserPui1 puid: '+puid+' update key: '+k+' value: '+userBig[k])
            # user_big 中的新
            else:
                for k in oldUserAll.keys():
                    # userBig 中不是默认值的，都写过来
                    if hasattr(userBig, k) and (not isDbUserBigDefaultValue(userBig[k])) and hasattr(oldUserAll, k):
                        oldUserAll[k] = userBig[k]
                        logInfo('updateFromUserBig2UserPui2 puid: '+puid+' update key: '+k+' value: '+oldUserAll[k])
            dbUserDict = oldUserAll
            
        dbuser = dbUser(dbUserDict, puid, isgroup)
        return dbAddOrUpdateUserAll(dbd, dbuser)
    except Exception as e:
        dealException()
        logError('updateFromUserBig2UserPui err: '+str(e))
        return False
    